using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Materials;

namespace Beaker.OpenCube.Render.Shapes
{
	public sealed class Triangle
		: Shape
	{
		private readonly Point3 _v0;
		private readonly Point3 _v1;
		private readonly Point3 _v2;
		private readonly Vector3 _normal;
		private readonly IMaterial _material;

		public Triangle( Point3 a, Point3 b, Point3 c, IMaterial material )
		{
			// three points of triangle
			_v0 = a;
			_v1 = b;
			_v2 = c;

			// cross product of the two vectors of the 'legs'
			// results in the normal for the triangle
			// using counter-clockwise naming of points for the facing face
			_normal = ( ( _v1 - _v0 ) ^ ( _v2 - _v0 ) ).Normalize();

			_material = material;
		}

		/// <summary>
		/// Computes the intersection with the given ray.
		/// </summary>
		/// <param name="scene">The scene.</param>
		/// <param name="ray">The ray to compute an intersection with.</param>
		/// <returns>
		/// An intersection or <c>null</c> when there was no intersection.
		/// </returns>
		public override Intersection ComputeIntersection( Scene scene, Ray ray )
		{
			double a = _v0.X - _v1.X;
			double b = _v0.X - _v2.X;
			double c = ray.Direction.X;
			double d = _v0.X - ray.Origin.X;

			double e = _v0.Y - _v1.Y;
			double f = _v0.Y - _v2.Y;
			double g = ray.Direction.Y;
			double h = _v0.Y - ray.Origin.Y;

			double i = _v0.Z - _v1.Z;
			double j = _v0.Z - _v2.Z;
			double k = ray.Direction.Z;
			double l = _v0.Z - ray.Origin.Z;

			double m = f * k - g * j;
			double n = h * k - g * l;
			double p = f * l - h * j;
			double q = g * i - e * k;
			double s = e * j - f * i;

			double invDenom = 1d / ( a * m + b * q + c * s );

			double e1 = d * m - b * n - c * p;
			double beta = e1 * invDenom;

			if( beta < 0d )
				return null;

			double r = e * l - h * i;
			double e2 = a * n + d * q + c * r;
			double gamma = e2 * invDenom;

			if( gamma < 0d )
				return null;

			if( beta + gamma > 1d )
				return null;

			double e3 = a * p - b * r + d * s;
			double t = e3 * invDenom;

			if( t < double.Epsilon )
				return null;

			return
				new Intersection(
					scene,
					this,
					ray,
					t,
					_normal,
					_material
					);
		}
	}
}